C# 7 New Features

Refer to New Features in C# 7.0

GitHub Demo Repo: https://github.com/garyng/csharp_7_new_features

Xamarin Workbook

A much better way to explore C# 7.0 new features: https://github.com/xamarin/Workbooks/blob/master/csharp/csharp7/csharp7.workbook

Code

Out Variables

[TestClass]
public class Out_Variables
{
    [TestMethod]
    public void Old_Out_Variables()
    {
        // have to "predeclare"
        int num;
        int.TryParse("123", out num);
        num.Should().Be(123);
    }

    [TestMethod]
    public void New_Out_Variables()
    {
        int.TryParse("123", out int num);
        // subsequent line can use 'num'
        num.Should().Be(123);
    }

    [TestMethod]
    public void New_Out_Variables_Using_Var()
    {
        // compiler can figure out what is the type
        int.TryParse("123", out var num);
        num.Should().Be(123);
    }

    [TestMethod]
    public void Discard_Out_Variables()
    {
        // only the return value is stored
        bool success = int.TryParse("123", out _);
    }
}

Deconstruction

[TestClass]
public class Deconstruction
{
    [TestMethod]
    public void Deconstruct_Returned_Tuples()
    {
        (string first, string middle, string last) = LookupName("123123");

        first.Should().Be("123");
        middle.Should().Be("456");
        last.Should().Be("789");
    }

    [TestMethod]
    public void Deconstruct_Returned_Tuples_Using_Multiple_Var()
    {
        (var first, var middle, var last) = LookupName("123123");

        first.Should().Be("123");
        middle.Should().Be("456");
        last.Should().Be("789");
    }

    [TestMethod]
    public void Deconstruct_Returned_Tuples_Using_Single_Var()
    {
        var (first, middle, last) = LookupName("123123");

        first.Should().Be("123");
        middle.Should().Be("456");
        last.Should().Be("789");
    }

    (string, string, string) LookupName(string id)
    {
        return ("123", "456", "789");
    }

    [TestMethod]
    public void Deconstructing_Other_Type()
    {
        // Deconstruction is not just for tuples.
        // Any type can be deconstructed, as long as it has an (instance or extension) deconstructor method
        // of the form:
        // public void Deconstruct(out T1 x1, ..., out Tn xn) { ... }

        var (x, y) = new Point(1, 2);
        x.Should().Be(1);
        y.Should().Be(2);
    }

    [TestMethod]
    public void Deconstruction_Other_Type_With_Discards()
    {
        var (x, _) = new Point(1, 2);
        x.Should().Be(1);
    }

    public class Point
    {
        public int X { get; set; }
        public int Y { get; set; }

        public Point(int x, int y)
        {
            X = x;
            Y = y;
        }

        public void Deconstruct(out int x, out int y)
        {
            x = X;
            y = Y;
        }
    }
}

More Expression Bodied Members

[TestClass]
public class Expression_Bodied_Members
{
  public class Person
  {
    private static ConcurrentDictionary<int, string> names = new ConcurrentDictionary<int, string>();
    private int id = 1;

    // Constructors
    public Person(string name) => names.TryAdd(id, name);

    // Deconstructors
    ~Person() => names.TryRemove(id, out _);

    public string Name
    {
      // Getters
      get => names[id];

      // Setters
      set => names[id] = value;
    }
  }
}

Generalized Async Return Types

Refer to SO: In C#7, how can I “roll my own” Task-like type to use with async?

Literal Improvements

[TestClass]
public class Literal_Improvements
{
  [TestMethod]
  public void Digit_Seperators_Have_No_Effect_On_The_Value()
  {
    var d = 123_456;
    d.Should().Be(123456);
  }

  [TestMethod]
  public void Binary_Literals()
  {
    var b = 0b1111_1111;
    b.Should().Be(0xFF);
  }
}

Local Functions

[TestClass]
public class Local_Functions
{
  [TestMethod]
  public void Fibonacci_First_Term_Should_Be_1()
  {
    Fibonacci(0).Should().Be(1);
  }

  [TestMethod]
  public void Fibonacci_2nd_Term_Should_Be_1()
  {
    Fibonacci(1).Should().Be(1);
  }

  [TestMethod]
  public void Fibonacci_5th_Term_Should_Be_5()
  {
    Fibonacci(4).Should().Be(5);
  }

  public int Fibonacci(int x)
  {
    if (x < 0) throw new ArgumentException("Must larger than 0", nameof(x));
    return Fib(x).current;

    (int current, int previous) Fib(int i)
    {
      if (i == 0) return (1, 0);
      var (c, p) = Fib(i - 1);
      return (c + p, c);
    }
  }

  // other example from MSDN article: https://blogs.msdn.microsoft.com/dotnet/2017/03/09/new-features-in-c-7-0/

  public IEnumerable<T> Filter<T>(IEnumerable<T> source, Func<T, bool> filter)
  {
    if (source == null) throw new ArgumentNullException(nameof(source));
    if (filter == null) throw new ArgumentNullException(nameof(filter));

    return Iterator();

    IEnumerable<T> Iterator()
    {
      foreach (var element in source)
      {
        if (filter(element)) { yield return element; }
      }
    }
  }
}

Pattern Matching

Not very well written, refer to original article on .NET Blog

[TestClass]
public class Pattern_Matching
{
  // test a value whether it has certain "shape"
  // and extract information from the value when it does

  [TestMethod]
  public void Null_Variable_Should_Be_Null()
  {
    object o = null;
    bool check = o is null;
    check.Should().BeTrue();
  }

  [TestMethod]
  public void Int_Variable_Should_Be_Int()
  {
    object o = 123;
    bool check = o is int;
    check.Should().BeTrue();
  }

  [TestMethod]
  public void Non_Int_Variable_Should_Not_Be_Int()
  {
    object o = "123";
    bool check = o is int;
    check.Should().BeFalse();
  }

  [TestMethod]
  public void Pattern_Variables()
  {
    object o = 123;
    // must ensure o is an int
    // otherwise compiler will throw "Use of unassigned local variable i"
    //bool check = o is int i;
    //i.Should().Be(123);

    // i is introduced by a pattern
    // it is called a "pattern variable"
    if (o is int i)
    {
      i.Should().Be(123);
    }
    else
    {
      Assert.Fail();
    }
  }

  [TestClass]
  public class Patterns_With_TryXXX_Methods
  {
    [TestMethod]
    public void Object_Which_Is_An_Int_Should_Be_Unboxed_To_Int()
    {
      object o = 123;
      if (o is int i ||
        (o is string s && int.TryParse(s, out i)))
      {
        i.Should().Be(123);
      }
    }

    [TestMethod]
    public void Object_Which_Is_A_String_Should_Be_Parsed_To_Int()
    {
      object o = "123";
      if (o is int i ||
        (o is string s && int.TryParse(s, out i)))
      {
        i.Should().Be(123);
      }
    }
  }

  [TestClass]
  public class Switch_Statements_With_Patterns
  {
    // 1. the order of case clauses now matters
    // 2. the default clause is always evaluated last

    [TestMethod]
    public void Shape_Is_A_Circle()
    {
      Shape shape = new Circle()
      {
        Radius = 10
      };

      switch (shape)
      {
        case Circle c:
          c.Radius.Should().Be(10);
          break;
        case Rectangle r when (shape.Length == shape.Height):
          r.Length.Should().Be(r.Height);
          break;
        case Rectangle r:
          r.Length.Should().Be(10);
          r.Length.Should().Be(20);
          break;
        default:
          Assert.Fail("Unknown shape");
          break;
        case null:
          Assert.Fail("Should not be null");
          break;
      }
    }

    [TestMethod]
    public void Shape_Is_A_Square()
    {
      Shape shape = new Rectangle()
      {
        Length = 10,
        Height = 10
      };

      switch (shape)
      {
        case Circle c:
          c.Radius.Should().Be(10);
          break;
        case Rectangle r when (shape.Length == shape.Height):
          r.Length.Should().Be(r.Height);
          break;
        case Rectangle r:
          r.Length.Should().Be(10);
          r.Height.Should().Be(20);
          break;
        default:
          Assert.Fail("Unknown shape");
          break;
        case null:
          Assert.Fail("Should not be null");
          break;
      }
    }

    [TestMethod]
    public void Shape_Is_A_Rectangle()
    {
      Shape shape = new Rectangle()
      {
        Length = 10,
        Height = 20
      };

      switch (shape)
      {
        case Circle c:
          c.Radius.Should().Be(10);
          break;
        case Rectangle r when (shape.Length == shape.Height):
          r.Length.Should().Be(r.Height);
          break;
        case Rectangle r:
          r.Length.Should().Be(10);
          r.Height.Should().Be(20);
          break;
        default:
          Assert.Fail("Unknown shape");
          break;
        case null:
          Assert.Fail("Should not be null");
          break;
      }
    }

    [TestMethod]
    public void Shape_Is_A_Null()
    {
      Shape shape = null;

      switch (shape)
      {
        case Circle c:
          c.Radius.Should().Be(10);
          break;
        case Rectangle r when (shape.Length == shape.Height):
          r.Length.Should().Be(r.Height);
          break;
        case Rectangle r:
          r.Length.Should().Be(10);
          r.Length.Should().Be(20);
          break;
        default:
          Assert.Fail("Unknown shape");
          break;
        case null:
          Assert.Fail("Should not be null");
          break;
      }
    }

    [TestMethod]
    public void Shape_Is_A_Not_A_Shape()
    {
      Shape shape = new NotAShape();

      switch (shape)
      {
        case Circle c:
          c.Radius.Should().Be(10);
          break;
        case Rectangle r when (shape.Length == shape.Height):
          r.Length.Should().Be(r.Height);
          break;
        case Rectangle r:
          r.Length.Should().Be(10);
          r.Length.Should().Be(20);
          break;
        default:
          Assert.Fail("Unknown shape");
          break;
        case null:
          Assert.Fail("Should not be null");
          break;
      }
    }

    public class Shape
    {
      public int Length { get; set; }
      public int Height { get; set; }
    }
    public class Circle : Shape
    {
      public int Radius { get; set; }
    }
    public class Rectangle : Shape
    {
    }
    public class NotAShape : Shape
    {

    }
  }
}

Ref Returns and Ref Locals

[TestClass]
public class Ref_Returns_And_Locals
{
  // Return value by reference
  // Store them by reference in local variables

  [TestMethod]
  public void Return_Value_By_Reference_And_Store_It_By_Reference()
  {
    int[] array = { 1, 2, 3, 4, 5, 6 };
    ref int found = ref Find(6, array);
    // change the value
    found = 12;
    array[5].Should().Be(12);
  }

  public ref int Find(int num, int[] numbers)
  {
    foreach (int i in numbers)
    {
      if (numbers[i] == num)
      {
        // return the storage location not the value
        return ref numbers[i];
      }
    }

    throw new IndexOutOfRangeException($"{nameof(num)} not found");
  }
}

Throw Expression

[TestClass]
public class Throw_Expressions
{
  public class Person
  {
    public string Name { get; }

    public Person(string name) => Name = name ?? throw new ArgumentNullException(nameof(name));

    public string GetFirstName()
    {
      var parts = Name.Split(' ');
      return (parts.Length > 1) ? parts[0] : throw new InvalidOperationException("No name!");
    }

    public string GetLastName() => throw new NotImplementedException();
  }

  [TestMethod]
  [ExpectedException(typeof(ArgumentNullException))]
  public void Person_With_Null_Name_Should_Throw_Argument_Null_Exception()
  {
    Person p = new Person(null);
  }

  [TestMethod]
  [ExpectedException(typeof(InvalidOperationException))]
  public void GetFirstName_Should_Throw_Invalid_Operation_Exception_When_Name_Is_Empty()
  {
    Person p = new Person("123");
    p.GetFirstName();
  }
}

Tuples

[TestClass]
public class Tuples
{
  // require NuGet package "System.ValueTuple" to be installed

  [TestMethod]
  public void Tuple_With_Default_Names()
  {
    var names = LookupName("123123");

    names.Item1.Should().Be("123");
    names.Item2.Should().Be("456");
    names.Item3.Should().Be("789");
  }

  (string, string, string) LookupName(string id)
  {
    return ("123", "456", "789");
  }

  [TestMethod]
  public void Tuple_With_Descriptive_Names()
  {
    var names = LookupNameDescriptive("123123");

    names.First.Should().Be("123");
    names.Middle.Should().Be("456");
    names.Last.Should().Be("789");
  }

  (string First, string Middle, string Last) LookupNameDescriptive(string id)
  {
    return ("123", "456", "789");
  }

}

results matching ""

    No results matching ""